package os;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;

/**
 * Equivalent to a counting semaphore implemented in MiniOS. You can add or
 * change anything except the prototypes of the constructor, P and V methods.
 * 
 * @author Ahmed El-Eryan
 */
public class MiniSemaphore
{
	/**
	 * Access monitor to the wait and signal functions of the Semaphore.
	 */
	private MiniMonitor monitor;
	/**
	 * The waiting threads for the critical section.
	 */
	private LinkedList<Thread> queue;
	/**
	 * The dequeued threads for the critical section.
	 */
	private Set<Thread> dequed;
	// XXX: Implement the MiniSemaphore class

	/**
	 * Value of the semaphore.
	 */
	public int c;

	/**
	 * Constructor to MiniSemaphore class.
	 * 
	 * @param c initial value of the semaphore.
	 */
	public MiniSemaphore(int c)
	{
		this.c = c;
		monitor = new MiniMonitor();
		queue = new LinkedList<Thread>();
		dequed = new HashSet<Thread>();
	}

	/**
	 * <b>UnImplemented</b>: Wait function of the semaphore.
	 */
	public synchronized void P()
	{
//		System.err.println("                                              counting = "+c);
		monitor.enter();
		c--;
		if (this.c < 0)
		{
			try
			{
				queue.addLast(Thread.currentThread());
//				System.err.println(Thread.currentThread().getName() + " enqueued");
//				System.err.println(queue.toString());
				do
				{
					monitor.leave();
					wait();
					monitor.enter();
				} while (! dequed.contains(Thread.currentThread()));
				dequed.remove(Thread.currentThread());
//				System.err.println(Thread.currentThread().getName() + " dequed");
//				System.err.println(queue.toString());
			} 
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
		monitor.leave();
	}

	/**
	 * <b>UnImplemented</b>: Signal function of the semaphore.
	 */
	public synchronized void V()
	{
		monitor.enter();
		if (c < 0)
		{
			dequed.add(queue.removeFirst());
			notifyAll();
		}
		c++;
		monitor.leave();
	}
}
